package com.yunkeji.api.auth.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.spring.SpringUtil;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yunkeji.api.auth.bo.ChangeTokenBo;
import com.yunkeji.api.auth.bo.SendCodeBo;
import com.yunkeji.api.auth.bo.ToLoginBo;
import com.yunkeji.api.auth.bo.ToRegBo;
import com.yunkeji.api.auth.processor.service.LoginFactory;
import com.yunkeji.api.auth.processor.service.LoginStrategy;
import com.yunkeji.api.auth.service.AuthService;
import com.yunkeji.api.auth.vo.AppVipVo;
import com.yunkeji.api.auth.vo.ToLoginVo;
import com.yunkeji.api.dataUpload.service.SysUploadService;
import com.yunkeji.api.user.vo.UserInfoVo;
import com.yunkeji.common.constant.CacheConstants;
import com.yunkeji.common.constant.Constants;
import com.yunkeji.common.core.domain.event.LogininforEvent;
import com.yunkeji.common.core.domain.model.LoginUser;
import com.yunkeji.common.enums.DeviceType;
import com.yunkeji.common.enums.UserType;
import com.yunkeji.common.enums.app.AccountTypeEnums;
import com.yunkeji.common.enums.app.AppUserStatus;
import com.yunkeji.common.exception.AppServiceException;
import com.yunkeji.common.helper.LoginHelper;
import com.yunkeji.common.msg.PushMessage;
import com.yunkeji.common.utils.*;
import com.yunkeji.common.utils.redis.RedisUtils;
import com.yunkeji.common.utils.spring.SpringUtils;
import com.yunkeji.dao.domain.*;
import com.yunkeji.dao.mapper.AppUserExtMapper;
import com.yunkeji.dao.mapper.AppUserMapper;
import com.yunkeji.dao.mapper.AppUserWalletMapper;
import com.yunkeji.dao.mapper.UserInvitedMapper;
import com.yunkeji.dao.mapper.sys.SysIpDeviceBannedMapper;
import com.yunkeji.dao.vo.AppUserExtVo;
import com.yunkeji.dao.vo.AppUserVo;
import com.yunkeji.dao.vo.AppUserWalletVo;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 老张
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class AuthServiceImpl implements AuthService {

    private final AppUserMapper appUserMapper;

    private final AppUserExtMapper appUserExtMapper;

    private final AppUserWalletMapper appUserWalletMapper;

    @Autowired @Lazy AuthService authService;

    private final UserInvitedMapper userInvitedMapper;

    private final SysIpDeviceBannedMapper sysIpDeviceBannedMapper;

    private final SysUploadService uploadService;

    private static String EVN = SpringUtil.getActiveProfile();

    //  private final LockTask lockTask;

    @Value("${sa-token.timeout}")
    private Integer saTokenExpire;

    /**
     * 发送验证码
     *
     * @author 老张
     * @param bo
     * @return
     */
    @Override
    public void sendCode(SendCodeBo bo) {
        AppUser appUser =
                appUserMapper.selectOne(
                        Wrappers.lambdaQuery(AppUser.class)
                                .ne(AppUser::getStatus, AppUserStatus.永久删除.getCode())
                                .eq(AppUser::getPhone, bo.getPhone()));
        if (appUser != null) {
            if (appUser.getStatus().equals(AppUserStatus.封号.getCode())
                    && appUser.getLimitDate().getTime() < new Date().getTime()) {
                throw AppServiceException.paramException(
                        "账号已被封禁,暂无法登录,封禁截止时间:" + DateUtil.formatDateTime(appUser.getLimitDate()));
            }

            if (appUser.getAccountType() != AccountTypeEnums.用户.getCode()) {
                String code = "111111";
                // 设置缓存两分钟过期
                RedisUtils.setCacheObject(
                        CacheConstants.CAPTCHA_CODE_KEY + bo.getPhone(),
                        code,
                        Duration.ofMinutes(2));

                throw AppServiceException.paramException("请输入验证码:" + code);
            }
        }

        if (!PhoneUtil.isMobile(bo.getPhone())) {
            throw AppServiceException.paramException("手机号错误");
        }

        String code = String.valueOf(RandomUtil.randomInt(100000, 999999));
        // 设置缓存两分钟过期
        RedisUtils.setCacheObject(
                CacheConstants.CAPTCHA_CODE_KEY + bo.getPhone(), code, Duration.ofMinutes(2));

        if ("prod".equalsIgnoreCase(EVN)) {
            PushMessage.sendSMSCode(bo.getPhone(), code, String.valueOf(ServletUtils.getRequest().getHeader("branch").charAt(0)));
        }
    }

    /**
     * @author 老张
     * @param bo
     * @return
     */
    @Override
    public ToLoginVo toLogin(ToLoginBo bo) {
        LoginStrategy loginStrategy = LoginFactory.get(ToLoginBo.LoginType.getByCode(bo.getType()));
        if (loginStrategy != null) {
            return loginStrategy.execute(bo);
        } else {
            throw AppServiceException.paramException("登录出错.请稍后再试");
        }
    }

    /**
     * 刷新Kmsg的Token
     *
     * @author 老张
     * @return
     */
    @Override
    public String refreshKmsgToken(Long platformId) {
        return PushMessage.refreshKmsgToken(platformId);
    }

    /**
     * 开始登录
     *
     * @author 老张
     * @param bo
     * @param needCode 内部登录不需要验证码
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ToLoginVo toLogin(ToLoginBo bo, Boolean needCode) {
        checkCode(bo.getPhone(), bo.getCode(), needCode);
        AppUser appUser =
                appUserMapper.selectOne(
                        Wrappers.lambdaQuery(AppUser.class)
                                .ne(AppUser::getStatus, AppUserStatus.永久删除.getCode())
                                .eq(AppUser::getPhone, bo.getPhone()));
        ToLoginVo toLoginVo = new ToLoginVo();
        if (appUser != null) {
            // 检查是否已经登录过,登陆过需要清除之前的信息
            LoginHelper.removeLoginCacheInfo(appUser.getId());
            // 检查用户状态
            Integer status = appUser.getStatus();
            if (AppUserStatus.封号.getCode().equals(status)) {
                Date limitDate = appUser.getLimitDate();
                if (limitDate.getTime() >= new Date().getTime()) {
                    throw AppServiceException.accountException(
                            "用户已被封禁,截至时间:" + DateUtil.formatDateTime(limitDate));
                } else {
                    // 已经解封
                    // appUser.setStatus(AppUserStatus.正常.getCode());
                    appUserMapper.updateById(
                            new AppUser()
                                    .setId(appUser.getId())
                                    .setStatus(AppUserStatus.正常.getCode()));
                }
            }
            if (AppUserStatus.等待删除.getCode().equals(status)) {
                // 向系统提交了注销请求
                // appUser.setStatus(AppUserStatus.正常.getCode());
                appUserMapper.updateById(
                        new AppUser().setId(appUser.getId()).setStatus(AppUserStatus.正常.getCode()));
            }
            //获取当前ip
            // 已经存在用户
            UserInfoVo userInfoVo = BeanUtil.toBean(appUser, UserInfoVo.class);
            toLoginVo.setUserInfoVo(userInfoVo);
        } else {
            // 不存在用户去注册
            UserInfoVo userInfoVo = authService.regUser(bo);
            toLoginVo.setUserInfoVo(userInfoVo);
            appUser = BeanUtil.toBean(userInfoVo, AppUser.class);
        }
        // 查询钱包
        AppUserWallet appUserWallet = appUserWalletMapper.selectById(appUser.getId());
        if (appUserWallet == null) {
            throw AppServiceException.accountException("钱包信息异常");
        }
        toLoginVo.setUserWalletVo(BeanUtil.toBean(appUserWallet, AppUserWalletVo.class));
        // 查询扩展信息
        AppUserExtVo appUserExtVo = appUserExtMapper.selectVoById(appUser.getId());
        toLoginVo.setAppUserExtVo(appUserExtVo);
        // 构建登录信息
        LoginUser loginUser = buildLoginUser(appUser);
        // 补充参数,存入缓存
        loginUser.setSex(appUser.getSex());
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);
        // 记录登录信息
        recordLogininfor(
                loginUser.getUsername(),
                Constants.LOGIN_SUCCESS,
                MessageUtils.message("user.login.success"),
                UserType.APP_USER,
                appUser.getPlatformId());
        recordLoginInfo(appUser.getId(), appUser.getPhone());
        String tokenValue = StpUtil.getTokenValue();
        // 双Token机制
        toLoginVo.setToken("Bearer " + tokenValue);
        String refreshToken = SecureUtil.md5(tokenValue);
        RedisUtils.setCacheObject(
                CacheConstants.REFRESH_TOKEN + refreshToken,
                appUser.getPhone(),
                Duration.ofSeconds(saTokenExpire * 2));
        toLoginVo.setRefreshToken(refreshToken);
        // 消息系统Token
        String kmsgToken = refreshKmsgToken(appUser.getPlatformId());
        toLoginVo.setKmsgToken(kmsgToken);
        // 删除验证码缓存
        //  RedisUtils.deleteObject(key);
        // vip信息
        AppUserVip appUserVip =
                AppUserVip.builder().build().selectById(toLoginVo.getUserInfoVo().getId());
        toLoginVo.setAppVipVo(BeanUtil.toBean(appUserVip, AppVipVo.class));
        return toLoginVo;
    }

    /**
     * friendsList
     *
     * @author 老张
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UserInfoVo regUser(ToLoginBo toLoginBo) {
        String phone = toLoginBo.getPhone();
        AppUserVo appUserVo =
                appUserMapper.selectVoOne(
                        Wrappers.lambdaQuery(AppUser.class)
                                .ne(AppUser::getStatus, AppUserStatus.永久删除.getCode())
                                .eq(true, AppUser::getPhone, phone));
        if (appUserVo != null) {
            throw AppServiceException.accountException("注册失败");
        }
        HttpServletRequest request = ServletUtils.getRequest();
        int branch = Integer.valueOf(request.getHeader("branch"));
        // 创建AppuserId
        AppUser appUser = new AppUser();
        // 验证邀请
        //        if (StrUtil.isNotEmpty(toLoginBo.getInvitedCode())) {
        //            String key = CacheConstants.INVITED + toLoginBo.getInvitedCode();
        //            Object cacheObject = RedisUtils.getCacheObject(key);
        //            if (cacheObject != null) {
        //                Long pid = Long.valueOf(String.valueOf(cacheObject));
        //                AppUser pUser = appUserMapper.selectById(pid);
        //                if (pUser != null) {
        //                    appUser.setPid(pUser.getId());
        //                }
        //                // 删除邀请
        //                RedisUtils.deleteObject(key);
        //            }
        //        }
        appUser.setPassword(toLoginBo.getPassword());
        appUser.setPhone(phone);
        appUser.setBranch(branch);
        //  appUser.setPlatformId(onlyId);
        appUserMapper.insert(appUser);

        // 创建扩展信息
        AppUserExt appUserExt = new AppUserExt();
        appUserExt.setNickName(NameUtils.getName());
        appUserExt.setRegDate(new Date());
        appUserExt.setId(appUser.getId());
        appUserExtMapper.insert(appUserExt);
        // 创建VIP信息
        AppUserVip.builder().id(appUser.getId()).build().insert();

        // 获取一下数据库初始数据
        appUser = appUserMapper.selectById(appUser.getId());

        // 创建钱包
        AppUserWallet appUserWallet = new AppUserWallet();
        // 外键级联
        appUserWallet.setId(appUser.getId());
        appUserWallet.setPlatformId(appUser.getPlatformId());
        appUserWalletMapper.insert(appUserWallet);

        PushMessage.sendSysNotify(
                appUser.getPlatformId(), "恭喜您成功注册爱洞平台，请前往‘个人资料’完善资料信息，这样会增加您接通视频的曝光率哟~");

        // 上报数据
        uploadService.upload(request.getHeader("oaid"), DataUploadUtils.DataType.注册, branch);

        return BeanUtil.toBean(appUser, UserInfoVo.class);
    }

    @Override
    public ToLoginVo registerByCode(ToRegBo bo) {
        AppUser appUser =
                appUserMapper.selectOne(
                        Wrappers.lambdaQuery(AppUser.class)
                                .ne(AppUser::getStatus, AppUserStatus.永久删除.getCode())
                                .eq(AppUser::getPhone, bo.getPhone()));
        if (appUser != null) {
            throw AppServiceException.paramException("注册失败");
        }
        ToLoginBo toLoginBo = new ToLoginBo();
        toLoginBo.setPhone(bo.getPhone());
        toLoginBo.setCode(bo.getCode());
        toLoginBo.setPassword(BCrypt.hashpw(bo.getPassword()));
        return toLogin(toLoginBo, "prod".equalsIgnoreCase(EVN));
    }

    @Override
    public void resetPwd(ToRegBo bo) {
        AppUser appUser =
                appUserMapper.selectOne(
                        Wrappers.lambdaQuery(AppUser.class)
                                .ne(AppUser::getStatus, AppUserStatus.永久删除.getCode())
                                .eq(AppUser::getPhone, bo.getPhone()));
        if (appUser == null) {
            throw AppServiceException.paramException("重置密码失败.");
        }
        if (!appUser.getStatus().equals(AppUserStatus.正常.getCode())) {
            throw AppServiceException.paramException("重置密码失败.");
        }
        checkCode(bo.getPhone(), bo.getCode(), "prod".equalsIgnoreCase(EVN));
        AppUser appUserNew = new AppUser();
        appUserNew.setId(appUser.getId());
        appUserNew.setPassword(BCrypt.hashpw(bo.getPassword()));
        appUserMapper.updateById(appUserNew);
    }

    /**
     * 检查验证码
     *
     * @author 老张
     */
    private void checkCode(String phone, String code, Boolean needCode) {
        String key = CacheConstants.CAPTCHA_CODE_KEY + phone;
        Object cacheObject = RedisUtils.getCacheObject(key);
        if (needCode) {
            if (cacheObject == null) {
                throw AppServiceException.paramException("验证码错误");
            }
            String redisCode = (String) cacheObject;
            if (!redisCode.equals(code)) {
                throw AppServiceException.paramException("验证码错误");
            }
            RedisUtils.deleteObject(key);
        }
    }

    /**
     * 记录登录信息
     *
     * @author 老张
     * @param username 用户名
     * @param status 状态
     * @param message 消息内容
     */
    private void recordLogininfor(
            String username, String status, String message, UserType userType, Long platformId) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setUserType(userType);
        logininforEvent.setPlatformId(platformId);

        // 客户端信息
        HttpServletRequest request = ServletUtils.getRequest();
        // 登陆渠道号
        logininforEvent.setBranch(Integer.parseInt(request.getHeader("branch")));
        logininforEvent.setVersion(request.getHeader("serial"));
        // 设备型号
        logininforEvent.setModel(request.getHeader("model"));
        // 设备系统
        logininforEvent.setOs(request.getHeader("os"));
        // 设备唯一号
        logininforEvent.setDeviceToken(request.getHeader("deviceToken"));

        logininforEvent.setRequest(request);
        SpringUtils.context().publishEvent(logininforEvent);
    }

    /**
     * 构建登录用户
     *
     * @author 老张
     */
    private LoginUser buildLoginUser(AppUser user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setPlatformId(user.getPlatformId());
        loginUser.setAccountType(user.getAccountType());
        loginUser.setUserId(user.getId());
        loginUser.setUsername(user.getPhone());
        loginUser.setUserType(UserType.APP_USER.getUserType());
        return loginUser;
    }

    /**
     * 记录登录信息
     *
     * @author 老张
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId, String username) {
        AppUser sysUser = new AppUser();
        sysUser.setId(userId);
        sysUser.setUpdateBy(username);

        appUserMapper.updateById(sysUser);
        AppUserExt appUserExt = appUserExtMapper.selectById(sysUser.getId());

        appUserExtMapper.updateById(
                new AppUserExt()
                        .setId(appUserExt.getId())
                        .setLoginDate(DateUtils.getNowDate())
                        .setLoginIp(ServletUtils.getClientIP()));
    }

    /**
     * Token置换
     *
     * @author 老张
     * @param bo
     * @return
     */
    @Override
    public ToLoginVo changeToken(ChangeTokenBo bo) {
        Object cacheObject =
                RedisUtils.getCacheObject(CacheConstants.REFRESH_TOKEN + bo.getRefreshToken());
        if (cacheObject == null) {
            throw AppServiceException.reLoginException("置换失败请重新登录");
        }
        AppUser appUser = appUserMapper.selectById(bo.getUid());
        String phoneNum = (String) cacheObject;
        if (appUser == null || !phoneNum.equalsIgnoreCase(appUser.getPhone())) {
            throw AppServiceException.reLoginException("置换失败请重新登录");
        }
        ToLoginBo toLoginBo = new ToLoginBo();
        toLoginBo.setPhone(phoneNum);
        ToLoginVo toLoginVo = this.toLogin(toLoginBo, false);
        return toLoginVo;
    }
}
